package com.qihunet.springbootjwt.config;

import com.qihunet.springbootjwt.constant.ErrorDetail;
import com.qihunet.springbootjwt.constant.RedisKey;
import com.qihunet.springbootjwt.exception.BusinessException;
import com.qihunet.springbootjwt.persistent.entity.UserInfo;
import com.qihunet.springbootjwt.utils.RedisUtils;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.*;
import java.util.function.Function;

/**
 * @author gary
 * JwtTokenUtil负责执行JWT操作，比如创建和验证，它使用io.jsonwebtoken.Jwts来实现这一点。
 * The JwtTokenUtil is responsible for performing JWT operations like creation and validation.
 * It makes use of the io.jsonwebtoken.Jwts for achieving this.
 */
@Component
public class JwtTokenUtil implements Serializable {
    private static final long serialVersionUID = -2550185165626007488L;

    @Value("${jwt.validity}")
    private Integer jwtValidity;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.interimExpiration}")
    private Long interimExpiration;

    @Resource
    private RedisUtils redisUtils;

    /**
     *
     * @param token token信息
     * @param claimsResolver　
     * @param <T>　类型限定
     * @return　指定内容信息
     */
    private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) throws BusinessException {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    /**
     * 通过秘钥和获取token中存储的信息
     * @param token ＪＷＴ信息
     * @return Claims set
     */
    private Claims getAllClaimsFromToken(String token) throws BusinessException {
        Claims result = null;
        try {
            result = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();

        } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException e) {
            throw new BusinessException(ErrorDetail.AUTH_INVALID_TOKEN);
        }
        return result;
    }

    /**
     * 获取ｔｏｋｅｎ过期时间
     * @param token　jwt信息
     * @return　过期时间
     */
    private Date getExpirationDateFromToken(String token) throws BusinessException {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    /**
     * 检验ｔｏｋｅｎ是否过期
     * @param token jwt信息
     * @return 是否过期
     */
    private Boolean isTokenExpired(String token) throws BusinessException {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    /**
     * 生成token信息
     * @param platformUserInfo 用户信息
     * @param roles 角色信息
     * @return token信息
     */
    public String generateToken(UserInfo platformUserInfo, List<String> roles) {

        Map<String, Object> claims = new HashMap<>();
        claims.put("role",roles.toArray());
        claims.put("enabled", platformUserInfo.getIsEnabled());
        String displayUserId = platformUserInfo.getDisplayUserId();
        claims.put("id", displayUserId);
        claims.put("userType",platformUserInfo.getUserType());
        String token = doGenerateToken(claims, platformUserInfo.getUsername());
        redisUtils.set(RedisKey.LOGIN_USER_TOKEN + displayUserId + token, token, interimExpiration);
        return token;
    }

    /**
     * 执行生成ＪＷＴ token信息
     * @param claims　基础信息
     * @param username　用户名
     * @return　jwt token信息
     */
    private String doGenerateToken(Map<String, Object> claims, String username) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                //.setExpiration(new Date(System.currentTimeMillis() + jwtValidity * 1000))
                .signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    /**
     * 验证ｔｏｋｅｎ信息
     * @param token　token信息
     * @return　验证结果
     */
    Boolean validateToken(String token) throws BusinessException {
        String userDisplayId = getUserDisplayIdFromToken(token);
        if (userDisplayId == null) {
            return false;
        } else {
            return  isTokenNotExpired(userDisplayId, token);
        }
    }

    private String getUserDisplayIdFromToken(String token) throws BusinessException {
        JwtUser jwtUser = getUserInfoFromToken(token);
        return jwtUser.getDisplayUserId();
    }

    /**
     * 通过ｔｏｋｅｎ获取用户名
     * @param token jwt信息
     * @return　用户名
     */
    public String getUsernameFromToken(String token) throws BusinessException {
        return  getClaimFromToken(token, Claims::getSubject);
    }

    public JwtUser getUserInfoFromToken(String token)throws BusinessException {
        UserInfo platformUserInfo = new UserInfo();
        Claims claims = getAllClaimsFromToken(token);

        platformUserInfo.setDisplayUserId((String) claims.get("id"));
        platformUserInfo.setUsername((String)claims.get("sub"));
        platformUserInfo.setIsEnabled((Boolean) claims.get("enabled"));

        platformUserInfo.setUserType((Integer)claims.get("userType"));
        List<String> roles= Collections.singletonList("ROLE_ADMIN");
        //userInfoService.findUserPermissionByUserIdAndUserType(platformUserInfo);
        return JwtUserFactory.create(platformUserInfo,roles);
    }


    private Boolean isTokenNotExpired(String displayUserId, String authToken) throws BusinessException {
        Boolean tokenExpired = isTokenExpired(authToken);
        if(tokenExpired) {
            if (redisUtils.exists(RedisKey.LOGIN_USER_TOKEN + displayUserId + authToken)) {
                redisUtils.set(RedisKey.LOGIN_USER_TOKEN + displayUserId + authToken, authToken, interimExpiration);
                return true;
            }
            return false;
        }else{
            return false;
        }
    }
}
